﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace Crayons.Components
{
    public class Surface
    {
        public const int kShadowDistance = 6;
        protected const int kShadowSoftness = 4;
        protected const int kShadowOpacity = 100;

        protected Bitmap m_image;
        protected Byte[] m_buffer;
        protected Size m_size;
        protected Size m_client;
        protected Point m_position;
        protected Point m_viewPosition;
        protected Double m_scale;
        protected bool m_sizeChangedOnScale;
        protected bool m_axesVisibility;

        public Point Position
        {
            get 
            { 
                return m_position; 
            }
            
            set 
            { 
                m_position = value; 
            }
        }

        public Double ScaleRate
        {
            get 
            { 
                return m_scale; 
            }
            
            set 
            { 
                m_scale = value;
                RefreshSurface();
            }
        }

        public Point ViewLocation
        {
            get
            {
                return m_viewPosition;
            }

            set
            {
                m_viewPosition = value;
            }
        }

        public Size ContentSize
        {
            get 
            { 
                return m_client; 
            }

            set
            {
                m_client = value;
                m_viewPosition = new Point(-value.Width / 5, -value.Height /5);
                m_buffer = new Byte[m_client.Width * m_client.Height * 4];
                RefreshSurface();
            }
        }

        public Bitmap ContentImage
        {
            get { return m_image; }
        }

        public Surface()
        {
            m_scale = 1.0f;
            m_sizeChangedOnScale = false;
            m_axesVisibility = true;
        }

        protected void RefreshSurface()
        {
            m_size.Width = (Int32)(m_client.Width * (m_sizeChangedOnScale ? m_scale : 1.0f)) + kShadowDistance;
            m_size.Height = (Int32)(m_client.Height * (m_sizeChangedOnScale ? m_scale : 1.0f)) + kShadowDistance;

            m_image = new Bitmap(m_size.Width, m_size.Height, PixelFormat.Format32bppArgb);
        }

        public Point ToSurfaceLocalCoord(Point pos)
        {
            return new Point((int)((pos.X - m_position.X) / m_scale) + m_viewPosition.X , 
                (int)((pos.Y - m_position.Y) / m_scale) + m_viewPosition.Y);
        }

        public Point FromSurfaceLocalCoord(Point pos)
        {
            return new Point((int)((pos.X - m_viewPosition.X) * m_scale) + m_position.X,
                (int)((pos.Y - m_viewPosition.Y) * m_scale) + m_position.Y);
        }

        public void ClearWithCheckerboardPattern()
        {
            Size scaled_client = new Size((Int32)(m_client.Width * (m_sizeChangedOnScale ? m_scale : 1.0f)),
                (Int32)(m_client.Height * (m_sizeChangedOnScale ? m_scale : 1.0f)));

            Bitmap bmShade = new Bitmap(scaled_client.Width / kShadowSoftness, scaled_client.Height / kShadowSoftness);

            Graphics gShade = Graphics.FromImage(bmShade);
            gShade.Clear(Color.FromArgb(kShadowOpacity, Color.Gray));

            Graphics gClient = Graphics.FromImage(m_image);
            gClient.Clear(Color.Transparent);

            gClient.DrawImage(bmShade,
                new Rectangle(kShadowDistance, kShadowDistance, scaled_client.Width, scaled_client.Height),
                new Rectangle(0, 0, bmShade.Width, bmShade.Height), 
                GraphicsUnit.Pixel);


            float[] dashValues = { 8, 8 };
            Pen dashPen = new Pen(Color.LightGray, 1);
            dashPen.DashPattern = dashValues;
            gClient.FillRectangle(new SolidBrush(Color.White), 0, 0, scaled_client.Width, scaled_client.Height);

            Point viewOrigin = new Point((int)(-m_viewPosition.X * m_scale), (int)(-m_viewPosition.Y * m_scale));
            int dx = (int)Math.Ceiling(viewOrigin.X / 16.0f);
            int dy = (int)Math.Ceiling(viewOrigin.Y / 16.0f);

            Point topLeft = new Point(viewOrigin.X - (16 * dx), viewOrigin.Y - (16 * dy));
            int line = 0;
            while ((topLeft.Y + line) < scaled_client.Height)
            {
                if ((topLeft.Y + line) >= 0)
                {
                    int xOffset = ((line / 8) % 2).Equals(0) ? 0 : 8;
                    gClient.DrawLine(dashPen, topLeft.X + xOffset, line + topLeft.Y, scaled_client.Width, line + topLeft.Y);
                }
                line++;
            }

            if (m_axesVisibility)
            {
                float[] axesDashValues = { 2, 2 };
                Pen axesDashPen = new Pen(Color.Red, 1);
                axesDashPen.DashPattern = axesDashValues;
                gClient.DrawLine(axesDashPen, 0, (int)(-m_viewPosition.Y * m_scale), scaled_client.Width, (int)(-m_viewPosition.Y * m_scale));
                gClient.DrawLine(axesDashPen, (int)(-m_viewPosition.X * m_scale), 0, (int)(-m_viewPosition.X * m_scale), scaled_client.Height);
            }

            gClient.Dispose();
            gShade.Dispose();
            bmShade.Dispose();
        }

        public void DrawBitmap(Bitmap bm, int x, int y)
        {
            Graphics gClient = Graphics.FromImage(m_image);
            gClient.Clip = new Region(new Rectangle(0, 0, m_client.Width, m_client.Height));
            gClient.SmoothingMode = SmoothingMode.None;
            gClient.InterpolationMode = InterpolationMode.NearestNeighbor;
            gClient.PixelOffsetMode = PixelOffsetMode.Default;
            gClient.ScaleTransform((float)m_scale, (float)m_scale);
            gClient.TranslateTransform(-0.5f, -0.5f);
            gClient.TranslateTransform(-m_viewPosition.X, -m_viewPosition.Y);
            gClient.DrawImageUnscaledAndClipped(bm, 
                Rectangle.FromLTRB(x, y , m_client.Width, m_client.Height));             
            gClient.Dispose();
        }

        public void DrawSelections(SelectionCollection se)
        {
            Graphics gClient = Graphics.FromImage(m_image);
            gClient.Clip = new Region(new Rectangle(0, 0, m_client.Width, m_client.Height));
            gClient.TranslateTransform((int)(-m_viewPosition.X * m_scale),(int)(-m_viewPosition.Y * m_scale));
            se.Flush(gClient);
            gClient.Dispose();
        }

        public void DrawArgb(byte[] bgra, int x, int y, int width, int height)
        {
            Byte[] mod_bgra = Utils.Scale_NearestNeighbor(bgra, 
                new Size(width, height), 
                new Size((Int32)(width* m_scale), (Int32)(height * m_scale)));

            Size scaled_client = new Size((Int32)(m_client.Width * (m_sizeChangedOnScale ? m_scale : 1.0f)),
                (Int32)(m_client.Height * (m_sizeChangedOnScale ? m_scale : 1.0f)));

            BitmapData bmdata = m_image.LockBits(new Rectangle(0, 0, m_size.Width, m_size.Height), 
                ImageLockMode.ReadWrite, 
                m_image.PixelFormat);
            
            byte[] pixels = new byte[scaled_client.Width * scaled_client.Height * 4];

            for (int i = 0; i < scaled_client.Height; i++ )
                Marshal.Copy(bmdata.Scan0 + (i * m_size.Width * 4), pixels, i * scaled_client.Width * 4, scaled_client.Width * 4);

            Utils.Blend_AlphaBlending(mod_bgra, 
                new Point((Int32)(x * m_scale) , (Int32)(y * m_scale)), 
                new Size((Int32)(width * m_scale), (Int32)(height * m_scale)), 
                ref pixels, scaled_client);

            for (int i = 0; i < scaled_client.Height; i++ )
                Marshal.Copy(pixels, i * scaled_client.Width * 4, bmdata.Scan0 + (i * m_size.Width * 4), scaled_client.Width * 4);

            m_image.UnlockBits(bmdata);
        }
    }
}
